package com.stone.boot.pipeline.app.service.impl;

import com.stone.boot.pipeline.app.service.PipelineNodeService;
import com.stone.boot.pipeline.app.service.PipelineRedisService;
import com.stone.boot.pipeline.domain.entity.PipelineNode;
import com.stone.boot.pipeline.domain.repository.PipelineNodeRepository;
import com.stone.starter.core.constant.BaseConstants;
import com.stone.starter.core.exception.CacheModifyException;
import com.stone.starter.core.exception.CommonException;
import com.stone.starter.core.helper.TransactionalHelper;
import com.stone.starter.core.util.list.ListUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.hzero.mybatis.domian.Condition;
import org.hzero.mybatis.util.Sqls;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

/**
 * 流程器节点service实现
 *
 * @author Mr_wenpan@163.com 2021/8/5 10:21 下午
 */
@Service
public class PipelineNodeServiceImpl implements PipelineNodeService {

    private final PipelineNodeRepository pipelineNodeRepository;

    private final TransactionalHelper transactionalHelper;

    private final PipelineRedisService pipelineRedisService;

    public PipelineNodeServiceImpl(PipelineNodeRepository pipelineNodeRepository,
                                   TransactionalHelper transactionalHelper,
                                   PipelineRedisService pipelineRedisService) {
        this.pipelineRedisService = pipelineRedisService;
        this.transactionalHelper = transactionalHelper;
        this.pipelineNodeRepository = pipelineNodeRepository;
    }

    @Override
    public int batchMerge(List<PipelineNode> pipelineNodes) {
        int existNum = 0;
        for (PipelineNode pipelineNode : pipelineNodes) {
            // 非空字段校验
            pipelineNode.validate();
            // 下个节点行为和决策类型是否重复
            boolean exist = pipelineNode.exist(pipelineNodeRepository);
            // 如果存在，则将存在数量++
            if (exist) {
                existNum++;
                continue;
            }
            transactionalHelper.transactionOperation(() -> {
                if (pipelineNode.getId() != null) {
                    pipelineNodeRepository.updateByPrimaryKey(pipelineNode);
                    boolean isSuccess = pipelineRedisService.saveRedisPipelineNodeConf(pipelineNode.getPipelineId());
                    // 如果lua脚本更新Redis执行失败了，则抛出异常回滚数据保证数据一致性
                    if (!isSuccess) {
                        throw new CacheModifyException("缓存更新异常，流水线节点id为 [%s]", pipelineNode.getId());
                    }
                } else {
                    pipelineNodeRepository.insertSelective(pipelineNode);
                    pipelineRedisService.createRedisPipelineNodeConf(pipelineNode.getPipelineId());
                }
            });
        }
        return existNum;
    }

    @Override
    public void batchCreateAndUpdate(List<PipelineNode> pipelineNodes) {
        if (CollectionUtils.isEmpty(pipelineNodes)) {
            return;
        }
        ImmutablePair<List<PipelineNode>, List<PipelineNode>> pair =
                ListUtil.splitList(pipelineNodes, data -> Objects.isNull(data.getId()));
        transactionalHelper.transactionOperation(() -> {
            // 批量新增
            batchCreateCommon(pair.left);
            // 批量修改
            batchUpdateCommon(pair.right);
        });
    }

    @Override
    public void batchCreate(List<PipelineNode> pipelineNodes) {
        if (CollectionUtils.isEmpty(pipelineNodes)) {
            return;
        }
        batchCreateCommon(pipelineNodes);
    }

    @Override
    public void batchUpdate(List<PipelineNode> pipelineNodes) {
        if (CollectionUtils.isEmpty(pipelineNodes)) {
            return;
        }
        batchUpdateCommon(pipelineNodes);
    }

    @Override
    public void batchRemove(List<PipelineNode> pipelineNodes) {
        if (CollectionUtils.isEmpty(pipelineNodes)) {
            return;
        }
        // 先更新库在更新缓存，避免数据一致性问题
        transactionalHelper.transactionOperation(() -> {
            pipelineNodeRepository.batchDeleteByPrimaryKey(pipelineNodes);
            // 通过lua脚本更新Redis数据
            boolean isSuccess = pipelineRedisService.saveRedisPipelineNodeConf(pipelineNodes.get(0).getPipelineId());
            if (!isSuccess) {
                throw new CacheModifyException("缓存更新失败，请检查数据重试.");
            }
        });
    }

    /**
     * 批量更新流水线节点公用方法提取
     *
     * @param updateList 待更新的流水线节点集合
     * @author Mr_wenpan@163.com 2021/8/6 4:05 下午
     */
    private void batchUpdateCommon(List<PipelineNode> updateList) {
        // 必输参数校验
        updateList.forEach(PipelineNode::validate);
        transactionalHelper.transactionOperation(() -> {
            pipelineNodeRepository.batchUpdateByPrimaryKeySelective(updateList);
            updateList.forEach(e -> {
                boolean isSuccess = pipelineRedisService.saveRedisPipelineNodeConf(e.getPipelineId());
                // 如果lua脚本更新Redis执行失败了，则抛出异常回滚数据保证数据一致性
                if (!isSuccess) {
                    throw new CacheModifyException("缓存更新异常，流水线节点id为 [%s]", e.getId());
                }
            });
        });
    }

    /**
     * 批量新增流水线节点公用方法提取
     *
     * @param createList 待新增的流水线节点集合
     * @author Mr_wenpan@163.com 2021/8/6 4:06 下午
     */
    private void batchCreateCommon(List<PipelineNode> createList) {
        createList.forEach(PipelineNode::validate);
        String checkResult = validateData(createList);
        if (StringUtils.isNotBlank(checkResult)) {
            throw new CommonException("流程器节点重复，如下流程器节点已经在数据库存在，请修正：{}", checkResult);
        }
        transactionalHelper.transactionOperation(() -> {
            pipelineNodeRepository.batchInsertSelective(createList);
            createList.forEach(e -> pipelineRedisService.createRedisPipelineNodeConf(e.getPipelineId()));
        });
    }

    /**
     * 校验流水线节点是否已经存在于数据库中
     */
    private String validateData(List<PipelineNode> pipelineNodes) {
        if (CollectionUtils.isEmpty(pipelineNodes)) {
            return null;
        }
        StringJoiner joiner = new StringJoiner(BaseConstants.Symbol.COMMA);
        for (PipelineNode pipelineNode : pipelineNodes) {
            List<PipelineNode> nodeList = pipelineNodeRepository.selectByCondition(Condition.builder(PipelineNode.class)
                    .andWhere(Sqls.custom()
                            .andEqualTo(PipelineNode.FIELD_PIPELINE_ID, pipelineNode.getPipelineId())
                            .andEqualTo(PipelineNode.FIELD_CUR_ACTION, pipelineNode.getCurAction())
                            .andEqualTo(PipelineNode.FIELD_STRATEGY_TYPE, pipelineNode.getStrategyType()))
                    .build());
            if (CollectionUtils.isNotEmpty(nodeList)) {
                PipelineNode node = nodeList.get(0);
                String message = node.getPipelineId() + BaseConstants.Symbol.MIDDLE_LINE +
                        node.getCurAction() + BaseConstants.Symbol.MIDDLE_LINE + node.getStrategyType();
                joiner.add(message);
            }
        }
        return joiner.toString();
    }

}